-module(aleph_dht_table). 
-include("aleph_data_interface.hrl").
-include_lib("stdlib/include/qlc.hrl"). 
-include_lib("mnemosyne/include/mnemosyne.hrl").
-include("aleph_db_macros.hrl"). 
-behaviour(gen_server).
-export([handle_call/3,handle_info/2,terminate/2,handle_cast/2,         
        init/1,start_link/1,start/1,process_spec/1]).
-export([initial_db/0,clear_buckets/0,change_root/1,root/0,root_id/0,root_num/0,close/0,force_dump/0,find_node/1,
         replace_stale_node/2,insert_node/2,just_seen_node/1,invalidate_node/1,node_failed/1]).
-export([initial_db/1,clear_buckets/1,dump_to_db/1,change_root/2,root/1,root_id/1,root_num/1,retreive/1,close/1,force_dump/1,find_node/2,
         replace_stale_node/3,insert_node/3,just_seen_node/2,invalidate_node/2,node_failed/2]).
-export([insert/3,locate_bucket/1,split_bucket/1,replace_node/3,search_ets/1,
         insert_ets/1,find/1]).
-import(aleph_config,[get_dump_db_interval/0]).
-import(aleph_utility,[foldl_with_break/3,while/2,int_pow/2]).
-import(aleph_process_labeler,[label_process/1]).
-include("aleph_service_macros.hrl").
-include("aleph_naming_macros.hrl").
 
    
%%%%%%%%%%%%%interface defnition.
-define(SERVICE_NAME,?DHT_TABLE_PROCESS).
?define_call(init).
?define_cast(close).
?define_call(force_dump).
?define_call(root).
?define_call1(change_root,NewRoot).
?define_call(root_id).
?define_call(root_num).
?define_call1(find_node,Id).
?define_call2(replace_stale_node,Old,New).
?define_call2(insert_node,Node,Contacted).
?define_call1(just_seen_node,Id).
?define_call1(invalidate_node,Node).
?define_call1(node_failed,Node).
?define_call(clear_buckets).
?define_call(initial_db).
%%%%%%%%%%%%%static functions
process_spec(Args)->
    {?DHT_TABLE_PROCESS,{aleph_dht_table,start_link,Args},transient, brutal_kill,worker, [aleph_dht_table]}.
%%%%%%%%%%%%%behaviour implementations
start_link(Node) ->      
   case label_process(
                     gen_server:start_link({local,?DHT_TABLE_PROCESS},aleph_dht_table, [Node], [])) of
        Ret={ok,_}->init(),Ret;
        Ret->Ret
   end.

start(Node)->  
    label_process(
                  gen_server:start({local,?DHT_TABLE_PROCESS},aleph_dht_table,[Node],[] )).
close(State)->
    erlang:exit(self(),normal),
    {noreply,State}.
%%%%%%%%%%%%%callback implementations
init({Node,_})-> 
   initial_db(none),     
   Timer=timer:send_after(get_dump_db_interval(),self(),{dump_to_db}),
   insert(Node,Node,true),
   retreive(Node),  
   {reply,ok,{Node,Timer}};
init([Node]) ->        
   process_flag(trap_exit, true),
   {ok,{Node,0}}.
    
?gen_handle_call.
?gen_handle_cast.  
?gen_handle_info;
handle_info({'EXIT',_,Reason}, State) ->    
    {stop, Reason, State}. 
 
terminate(_,State)->
    dump_to_db(State),
    io:format("dht_table_process_closed~n"),
    ok. 

%%%%%db functions
retreive(Root)->
    Qlc=?m_orderby([X||X<-mnesia:table(?DHT_NODE_TAB)],#dht_node.num),  
    AllNodes=lists:flatten(?retrieve(Qlc)),
    %io:format("insert~p~n",[AllNodes]),
    lists:foreach(fun(T)->%io:format("retreive ~p~n",[T]),
        insert(Root,dht_node:object(T),false)end,AllNodes).
     
dump_to_db({Root,Timer})->      
    AllBuckets=?e_retrieve([B(nodes)||{_,_,B}<-ets:table(?DHT_BUCKET_TAB)]),    
    AllNodes=[N(self)||B<-AllBuckets,length(B)=/=0,{_,N}<-B],   
    mnesia:transaction(fun()->lists:foreach(
                       fun(X)->%io:format("write ~p ~n",[X]),
                           mnesia:write(?DHT_NODE_TAB,X,write)end,AllNodes)
                       end),
    timer:cancel(Timer),
    NewTimer=timer:send_after(get_dump_db_interval(),self(),{dump_to_db}),    
    {noreply,{Root,NewTimer}}.
  
force_dump(State)->
    {_,NewState}=dump_to_db(State),
    {reply,ok,NewState}.

initial_db(State)->   
   try
      clear_buckets(State),
      ets:new(?DHT_BUCKET_TAB,[named_table,public,ordered_set,{keypos,1}]),
      insert_ets(dht_bucket:create(int_pow(2,160),0,[],now())),
      try
        mnesia:table_info(?DHT_NODE_TAB,all)      
      catch     
        exit:_->mnesia:create_table(?DHT_NODE_TAB,?db_options(dht_node,[num]))    
      end,
      {reply,{atomic,ok},State}
   catch
    exit:_->{reply,{atomic,error},State};
    error:_->{reply,{atomic,error},State}
   end.

%%%%%%%%%%%%%%%%%%%%%%%%%%%% 
%find_node interface
%%%%%%%%%%%%%%%%%%%%%%%%%%%% 
find_node(State,Id)->  
    {reply,find(Id),State}.
%%%%find_node helpers
get_nodes(?ETS_END)->[];
get_nodes(X)->(search_ets(X))(nodes).
get_nebour_nodes(?ETS_END,?ETS_END,ACC)->ACC;
get_nebour_nodes(Next,Prev,ACC)->
     case length(ACC)<?BUCKET_SIZE of
           false->ACC;
           true->get_nebour_nodes(ets:next(?DHT_BUCKET_TAB,Next),
                                  ets:prev(?DHT_BUCKET_TAB,Prev),
                                  ACC++get_nodes(Next)++get_nodes(Prev))                   
      end.
%%%%find_node implementation
find(Id)->
   Bucket=locate_bucket(Id),
   Nodes=Bucket(nodes),
   Key=Bucket(max),  
   case lists:keysearch(Id,1,Nodes) of
     {value,{_,Node}}->[Node];
     false->Ret=[N||{_,N}<-
                case length(Nodes)< ?BUCKET_SIZE of 
                  true->get_nebour_nodes(ets:next(?DHT_BUCKET_TAB,Key),
                                         ets:prev(?DHT_BUCKET_TAB,Key),
                                         Nodes);
                  false->Nodes  
                end],   
            lists:sublist(lists:sort(fun(A,B)->
            (Id bxor A(num))<(Id bxor B(num)) end,Ret),?BUCKET_SIZE)
   end.   

%%%%%%%%%%%%%%%%%%%%%%%%%%%% 
%replace_stale_node interface
%%%%%%%%%%%%%%%%%%%%%%%%%%%% 
replace_stale_node(State,Stale,New)->    
     Bucket=locate_bucket(Stale(num)),
     replace_node(Bucket,Stale,New),
     {reply,ok,State}.
%%%%replace_stale_node  helpers  
replace_node(Bucket,Node,none)->     
     insert_ets(Bucket({nodes,lists:delete(?NODE(Node),Bucket(nodes))}));
replace_node(Bucket,Node,NewNode)->
     insert_ets(Bucket({nodes,lists:keyreplace(Node(num),1,Bucket(nodes),?NODE(NewNode))})).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%   
%insert_node interface 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%  
insert_node({Root,_}=State,Node,Contacted)->
    case Root(id)=:=Node(id) of
        false->{reply,insert(Root,Node,Contacted),State};
        true->{reply,{ok,Root},State}
    end.
%%%%insert_node helpers
insert(Bucket,Node,Nodes,Contacted)->
     InsertNode=if Contacted==true->
                   aleph_dht_node:update_last_seen(Node); 
                   true->Node 
                end,
     AppenedNodes=lists:reverse([?NODE(InsertNode)|lists:reverse(lists:delete(?NODE(Node),Nodes))]),
     insert_ets(Bucket({[lastAccessed,nodes],[now(),AppenedNodes]})).

%%%%insert_node impelementation  
insert(Root,Node,Contacted)->
    Root_Num=Root(num),       
    Num=Node(num),
    Bucket=locate_bucket(Num),
    Max=Bucket(max),
    Min=Bucket(min),
    Nodes=Bucket(nodes),   
    case lists:keymember(Num,1,Nodes) of
         true->insert(Bucket,Node,Nodes,Contacted),              
               {ok};
         _-> case length(Nodes)< ?BUCKET_SIZE of
                  true->insert(Bucket,Node,Nodes,Contacted),%io:format("return none~n"),
                        {ok,none};
                   _->if Min>Root_Num;Max=<Root_Num->
                          [{_,H}|_]=Nodes,%io:format("Min:~p>Root_Num||Max:~p=<Root_Num:~p~n",[Min,Max,Root_Num]),
                          {ok,H};
                          true->split_bucket(Bucket),insert(Root,Node,Contacted)                                
                     end
              end    
    end.     
%%%%%%%%%%%%%%%%%%%%%%%%%%%% 
%just_seen_node interface
%%%%%%%%%%%%%%%%%%%%%%%%%%%%  
just_seen_node(State,Id)->     
   case find(Id) of
       []->{reply,none,State};
       [H|_]-> Bucket=locate_bucket(H(num)),               
               Tstamp=H(lastseen),               
               replace_node(Bucket,H,aleph_dht_node:update_last_seen(H)),                        
               {reply,Tstamp,State}
    end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%% 
%invalidate_node interface
%%%%%%%%%%%%%%%%%%%%%%%%%%%%       
invalidate_node(State,N)->
    replace_stale_node(State,N,none).           
%%%%%%%%%%%%%%%%%%%%%%%%%%%% 
%node_failed interface
%%%%%%%%%%%%%%%%%%%%%%%%%%%% 
node_failed(State,N)->
    case find(N(num)) of 
       []->{reply,none,State}; 
       [H|_]-> Failed_node=aleph_dht_node:msg_failed(H),                
                case Failed_node(fails)>?MAX_FAILURES of
                true->invalidate_node(State,N),{reply,Failed_node,State};
                false->replace_node(locate_bucket(H(num)),H,Failed_node),
                       {reply,none,State}
                end         
    end.    
%%%%%%%%%%%%%%%%%%%%%%%%%%%% 
%clear_buckets interface
%%%%%%%%%%%%%%%%%%%%%%%%%%%%    
clear_buckets(State)->
    Ret=case ets:info(?DHT_BUCKET_TAB) of
        [{_,_}|_]->ets:db_delete(?DHT_BUCKET_TAB);                      
        undefined->continue
    end,  
    {reply,Ret,State}.
%%%%%bucket helpers
split_bucket(Bucket)->
    Max=Bucket(max),        
    Bound=Max-((Max-Bucket(min)) div 2),
    %io:format("Bound~p~n",[Bound]),
    {Nodes1,Nodes2}=lists:partition(fun({_,X})->X(num)>=Bound end,Bucket(nodes)),
    %io:format("Nodes1:~p~n,Nodes2~p~n",[Nodes1,Nodes2]),
    insert_ets(dht_bucket:create(Bound,Bucket(min),Nodes2,now())),
    insert_ets(dht_bucket:create(Max,Bound,Nodes1,now())).

locate_bucket(Num)->    
   case ets:select(?DHT_BUCKET_TAB,[{{'$1','$2','$3'},[{'=<','$2',Num},{'>','$1',Num}],['$3']}]) of
       []->[];
       [H|_]->H
   end.
%%%%%ets helpers
search_ets(X)->[{_,_,Object}|_]=ets:lookup(?DHT_BUCKET_TAB,X),Object.

insert_ets(Bucket)->ets:insert(?DHT_BUCKET_TAB,?BUCKET(Bucket)).

%%%%%%%%%%%%%%%%%%%%%%%%%%%% 
%change_root interface
%%%%%%%%%%%%%%%%%%%%%%%%%%%%
change_root({_,Timer},NewRoot)->
    {reply,NewRoot,{NewRoot,Timer}}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%% 
%root interfaces
%%%%%%%%%%%%%%%%%%%%%%%%%%%%
root({Root,Timer})->
    {reply,Root,{Root,Timer}}.
root_id({Root,Timer})->
    {reply,Root(id),{Root,Timer}}.
root_num({Root,Timer})->
    {reply,Root(num),{Root,Timer}}.

    